/*****************************************************************************
 * set.h: quantization init
 *****************************************************************************
 * Copyright (C) 2003-2024 x264 project
 *
 * Authors: Loren Merritt <lorenm@u.washington.edu>
 *          Laurent Aimar <fenrir@via.ecp.fr>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02111, USA.
 *
 * This program is also available under a commercial proprietary license.
 * For more information, contact us at licensing@x264.com.
 *****************************************************************************/

#ifndef X264_SET_H
#define X264_SET_H

enum cqm4_e
{
    CQM_4IY = 0,
    CQM_4PY = 1,
    CQM_4IC = 2,
    CQM_4PC = 3
};
enum cqm8_e
{
    CQM_8IY = 0,
    CQM_8PY = 1,
    CQM_8IC = 2,
    CQM_8PC = 3,
};

typedef struct
{
	/*
		对应sps句法中的seq_parameter_set_id，取值范围[0,31]。用于在码流中标识一个唯一的sps。
		取值范围[0,31],如何实现唯一？例如有多个id为1的sps时，一般就是使用最近的那一个。
		大部分场景中，sps自始至终不会发生变化，i_id始终为0。
		在x264实现中，i_id从0开始递增，如果sps的成员发生变化，一般需要把i_id值加1并在码流中生成一个新的sps。
		pps的解析需要依赖sps，在pps中，就是通过这个seq_parameter_set_id（i_sps_id）来指向pps参考的sps。
	*/
    int i_id;

	/*
		对应sps句法中的profile_idc。
		enum profile_e
		{
			PROFILE_BASELINE = 66,
			PROFILE_MAIN     = 77,
			PROFILE_HIGH    = 100,
			PROFILE_HIGH10  = 110,
			PROFILE_HIGH422 = 122,
			PROFILE_HIGH444_PREDICTIVE = 244,
		};
		h264 spec中定义了很多profile，但x264只支持了上述几种。实际上任何编码器的实现都只是实现了h264 spec的一个子集。
		几乎不可能有哪个编码器会完整实现h264 spec，所以在研究代码的时候，关注代码实现的那部分spec就行了。
	*/
    int i_profile_idc;

	/*
		对应sps句法中的level_idc。
		
		| `i_level_idc` 参数值 | 对应级别 | 最大分辨率（像素）        | 最大帧率（fps） | 支持格式            | 样本率（samples/sec） |
		| ----------------- | ---- | ---------------- | --------- | --------------- | ---------------- |
		| 10                | 1    | QCIF（176×144）及以下 | 15        | 仅支持 QCIF 格式及以下  | 380160           |
		| 11                | 1.1  | CIF（352×288）及以下  | 30        | CIF 及以下         | 768000           |
		| 12                | 1.2  | CIF（352×288）及以下  | 30        | CIF 及以下         | 1536000          |
		| 13                | 1.3  | CIF（352×288）及以下  | 30        | CIF 及以下         | 3041280          |
		| 20                | 2    | CIF（352×288）及以下  | 30        | CIF 及以下         | 3041280          |
		| 21                | 2.1  | 支持 HHR 格式        | 30        | 启用 Interlace 支持 | 5068800          |
		| 22                | 2.2  | 支持 SD/4CIF 格式    | 30        | 启用 Interlace 支持 | 5184000          |
		| 30                | 3    | 支持 SD/4CIF 格式    | 30        | 启用 Interlace 支持 | 10368000         |
		| 31                | 3.1  | 支持 720p HD 格式    | 60        | 启用 Interlace 支持 | 27648000         |
		| 32                | 3.2  | 支持 SXGA 格式       | 60        | 启用 Interlace 支持 | 55296000         |
		| 40                | 4    | 支持 2Kx1K 格式      | 60        | 启用 Interlace 支持 | 62914560         |
		| 41                | 4.1  | 支持 2Kx1K 格式      | 60        | 启用 Interlace 支持 | 62914560         |
		| 42                | 4.2  | 支持 2Kx1K 格式      | 60        | 仅支持帧编码          | 125829120        |
		| 50                | 5    | 支持 3672x1536 格式  | 60        | 仅支持帧编码          | 150994944        |
		| 51                | 5.1  | 支持 4096x2304 格式  | 60        | 仅支持帧编码          | 251658240        |
			
		x264中i_level_idc及其影响范围矩阵。从这个表格看出一个level_idc值实际就是决定了分辨率最大值、帧率最大值、支持帧/interlace、最大样本率。
	*/
    int i_level_idc;

	/*
		用于决策码流是否遵守h264 spec Annex A.2中指定的约束项。
		决策机制/逻辑非常复杂，这里只列出我们常用的x264的决策，可以简化为：

		Baseline Profile（A.2.1）
		profile_idc = 66 或 b_constraint_set0 = 1 

		Constrained Baseline profile（A.2.1.1）
		profile_idc = 66 ​​且​​ b_constraint_set1 = 1 

		Main Profile(A.2.2)
		profile_idc = 77 且 b_constraint_set1 = 1

		只使用上述3中profile的情况下，可以认为b_constraint_set2/b_constraint_set3 始终为 0。
	*/
    int b_constraint_set0;
    int b_constraint_set1;
    int b_constraint_set2;
    int b_constraint_set3;

	/*
		对应sps句法中的  log2_max_frame_num_minus4（取值[0,12]）。
		从log2_max_frame_num_minus4的名字可以看出，实际写入sps的值是i_log2_max_frame_num-4。
		
		从i_log2_max_frame_num推导出变量MaxFrameNum = 2 ^ ( log2_max_frame_num_minus4 + 4 )。
		MaxFrameNum决定了slice_header句法中frame_num的最大值。
		
		在x264中，i_log2_max_frame_num默认值4，也就是slice_header.frame_num所指定的帧编号范围
		就是[0,15]。这个编号用于在流中定位一帧图像，最常用的就是定位参考帧。

		一句话，i_log2_max_frame_num决定码流中帧编号的最大值。帧编号用于在短时间内（回绕之前）唯一标识一帧。
	*/
    int i_log2_max_frame_num;

	/*
		对应sps句法中的pic_order_cnt_type。
		决定图像显示顺序（POC, Picture Order Count）的计算方法。POC是解码图像的显示顺序，一般<=GOP长度。
		在baseline时，i_poc_type=2，是最简单的一种计算方式，poc就是简单的单调增加（会乘以一个系数）然后根据GOP长度回绕。
	*/
    int i_poc_type;
    /* poc 0 */
	/*
		i_poc_type=0时，对应sps句法中的log2_max_pic_order_cnt_lsb_minus4，用来指定POC的最大值。  
	*/
    int i_log2_max_poc_lsb;

	/*
		对应sps句法中的max_num_ref_frames。
		baseline时，i_num_ref_frames默认值是3.
		指定参考帧最大数量。
	*/
    int i_num_ref_frames;

	/*
		对应sps句法中的gaps_in_frame_num_value_allowed_flag
		帧编号必须是连续的。解码器会严格检查帧编号的连续性，如果发现间隙，可能会认为出现了错误或帧丢失，并采取相应的措施（如请求重传或进行错误隐藏）
		b_gaps_in_frame_num_value_allowed始终为0，即帧编号必须连续。
	*/
    int b_gaps_in_frame_num_value_allowed;

	/*
		对应sps句法中的pic_width_in_mbs_minus1和pic_height_in_map_units_minus1。
		指输入图像以宏块为单位的宽度和高度。例如对1920x1080的原始图像来说（h264宏块大小是16x16），
		i_mb_width = 1920 / 16 = 120,
		i_mb_height = 1080 / 16 = 67.5 = 68(向上取整)
	*/
    int i_mb_width;
    int i_mb_height;

	/*
		对应sps句法中的frame_mbs_only_flag。表示是否只有帧宏块。
		baseline时，frame_mbs_only_flag=1。宏块的是按照从左到右，从上到下的正常顺序
		依次编号排列的。不涉及隔行的宏块。
	*/
    int b_frame_mbs_only;

	/*
		对应sps句法中的mb_adaptive_frame_field_flag。表示是否开启宏块帧场自适应。
		baseline时，frame_mbs_only_flag=1。b_mb_adaptive_frame_field=0。
	*/
    int b_mb_adaptive_frame_field;

	/*
		对应sps句法中的direct_8x8_inference_flag
		指导解码器如何为 B 帧中的“直接”预测模式（包括 B_Skip 和 B_Direct）推导出运动矢量。
		baseline时无B帧，暂不考虑。
	*/
    int b_direct8x8_inference;

	/*
		对应sps句法中的frame_cropping_flag
		frame_crop_left_offset/frame_crop_right_offset/frame_crop_top_offset/frame_crop_bottom_offset

		告诉解码器，解码后的YUV图像需要在上/下/左/右分别裁剪的像素数量。为什么需要裁剪？
		例如1920x1080的高度1080不是16的整数倍，因此在编码前编码器需要把输入的YUV数据进行拓展
		（因为h264编码是以宏块16x16为单位进行，所以输入图像的宽和高都必须是16的整数倍），拓展到
		1920x1088，一般是在图像下侧拓展，这样解码后的图像，就需要把拓展的部分数据裁剪调，否则多出来的数据
		显示出来就会是随机数据/全白/或者全黑这样的。
	*/
    int b_crop;
    struct
    {
        int i_left;
        int i_right;
        int i_top;
        int i_bottom;
    } crop;

	/*
		可选，忽略。
	*/
    int b_vui;
    struct
    {
        int b_aspect_ratio_info_present;
        int i_sar_width;
        int i_sar_height;

        int b_overscan_info_present;
        int b_overscan_info;

        int b_signal_type_present;
        int i_vidformat;
        int b_fullrange;
        int b_color_description_present;
        int i_colorprim;
        int i_transfer;
        int i_colmatrix;

        int b_chroma_loc_info_present;
        int i_chroma_loc_top;
        int i_chroma_loc_bottom;

        int b_timing_info_present;
        uint32_t i_num_units_in_tick;
        uint32_t i_time_scale;
        int b_fixed_frame_rate;

        int b_nal_hrd_parameters_present;
        int b_vcl_hrd_parameters_present;

        struct
        {
            int i_cpb_cnt;
            int i_bit_rate_scale;
            int i_cpb_size_scale;
            int i_bit_rate_value;
            int i_cpb_size_value;
            int i_bit_rate_unscaled;
            int i_cpb_size_unscaled;
            int b_cbr_hrd;

            int i_initial_cpb_removal_delay_length;
            int i_cpb_removal_delay_length;
            int i_dpb_output_delay_length;
            int i_time_offset_length;
        } hrd;

        int b_pic_struct_present;
        int b_bitstream_restriction;
        int b_motion_vectors_over_pic_boundaries;
        int i_max_bytes_per_pic_denom;
        int i_max_bits_per_mb_denom;
        int i_log2_max_mv_length_horizontal;
        int i_log2_max_mv_length_vertical;
        int i_num_reorder_frames;
        int i_max_dec_frame_buffering;

        /* FIXME to complete */
    } vui;

	/*
		baseline不使用，忽略。
	*/
    int b_qpprime_y_zero_transform_bypass;
    int i_chroma_format_idc;

	/*
		baseline不使用，忽略。
	*/
    int b_avcintra_hd;
    int b_avcintra_4k;
    int i_cqm_preset;
    const uint8_t *scaling_list[8]; /* could be 12, but we don't allow separate Cb/Cr lists */

} x264_sps_t;

typedef struct
{
	/*
		对应pps句法中的pic_parameter_set_id，取值范围[0,255]。用于在码流中标识一个唯一的pps。
		取值范围[0,255],如何实现唯一？例如有多个id为1的pps时，一般就是使用最近的那一个。
		大部分场景中，pps自始至终不会发生变化，i_id始终为0。
		在x264实现中，i_id从0开始递增，如果pps的成员发生变化，一般需要把i_id值加1并在码流中生成一个新的pps。
		pps的解析需要依赖sps，通过i_sps_id来指向被依赖的那个sps。
	*/
    int i_id;
	
	/*
		对应pps句法中的seq_parameter_set_id，指向被依赖的那个sps。
	*/
    int i_sps_id;

	/*
		对应pps句法中的entropy_coding_mode_flag。
		h.264的无损熵编码有2种，CAVLC和CABAC。作用是对量化后的数据无损压缩，进一步降低码率。
		baseline时，b_cabac=0，使用CAVLC。
	*/
    int b_cabac;

	/*
		对应pps句法中的bottom_field_pic_order_in_frame_present_flag。
		和底场图像相关。
		这里只考虑帧图像，不考虑场图像，b_pic_order=0。
	*/
    int b_pic_order;

	/*
		对应pps句法中的num_slice_groups_minus1。
		表示slice group的数量。h264中如果设置多个slice group，还需要指定slice group的分组方式。
		​​slice_group_map_type​​：定义宏块到Slice Group的映射方式（取值0-6），例如：

		类型0：交错分组。

		类型1：分散映射。

		类型2：前景与背景分组。

		类型3-6：自定义或变换映射

		但x264里面i_num_slice_groups始终为1，也就是一个图像帧的所有slice都被划分在一个slice group中。
		进一步，在baseline是，slice group只有1个，slice也只有一个，即图像帧的所有宏块都在一个slice中。
		这样，可以进一步方便对编码流程的理解。
	*/
    int i_num_slice_groups;

	/*
		对应pps句法中的num_ref_idx_l0_default_active_minus1。
		表示默认的L0列表中参考帧的最大数量。
		L0是前向参考列表，也就是帧号小于当前帧的那些参考帧，P/B帧都会用到。
		L1是后向参考列表，也就是帧号大于当前帧的那些参考帧，只有B帧会用到。
		
		在sps中也有一个i_num_ref_frames,这个是指定l0+l1最大的参考帧数量，告诉解码器需要提前分配好这么多个帧空间用来保存解码重建后的参考帧。
		i_num_ref_idx_l0_default_active表示默认的L0列表中参考帧的最大数量，也就是解码器可以参考这个值来预分配固定数量的帧空间给L0列表。
		
		在更内层的slice头中有num_ref_idx_active_override_flag用来表示是否覆盖PPS中的i_num_ref_idx_l0_default_active。
		如果num_ref_idx_active_override_flag是1，解码器在解码当前slice/帧的时候，最好根据slice头句法中的num_ref_idx_l0_active_minus1来分配空间。
		不过，我个人觉得，解码器不遵守slice头的这些规则也可以，只要按照i_num_ref_idx_l0_default_active/i_num_ref_idx_l1_default_active就够了。

		x264中i_num_ref_idx_l1_default_active固定为1，也就是B帧最多参考后向最近的一个P帧。
	*/
    int i_num_ref_idx_l0_default_active;
    int i_num_ref_idx_l1_default_active;

	/*
		对应pps句法中的weighted_pred_flag。
		
		假设一个短视频片段包含两个连续帧：
		​​参考帧（前一帧）​​：一个明亮的场景，例如阳光下的海滩。
		​​当前P帧（后一帧）​​：镜头快速切换到一个昏暗的室内场景。
		
		​​情况一：weighted_pred_flag = 0(禁用加权预测)​​
		解码器在预测当前昏暗的室内帧时，会直接使用之前明亮的沙滩帧作为参考。由于两者亮度差异巨大，
		直接相减会产生非常大的残差数据，编码器需要花费大量比特来编码这些残差，导致​​码率飙升​​，
		或者为了控制码率而被迫增大量化步长，导致​​画质下降​​。
		
		​​情况二：weighted_pred_flag = 1(启用加权预测)​​
		编码器分析到两帧之间存在显著的全局亮度变化，它会为参考帧（沙滩帧）计算一个​​小于1的权重（weight）​​ 
		和一个​​负的偏移量（offset）​​。解码器在得到这些参数后，会先对参考帧的像素值进行如下变换：

		预测值 = (权重 * 参考像素值) + 偏移量

		通过这个公式，编码器有效地“压暗”了参考帧，使其整体亮度更接近当前要编码的室内帧。
		这样得到的预测帧与当前帧的实际像素值更接近，​​残差数据大大减少​​。编码器可以用更少的比特来编码残差，
		从而在​​保持画质的同时降低了码率​​，或者在​​相同码率下获得更好的重建质量​​。

		权重和偏移量保存在slice头句法的pred_weight_table()中。

		至于如何计算出权重和偏移量，那是编码器内部实现的事，这个没有标准或规范明确如何做。只要解码器
		按照 预测值 = (权重 * 参考像素值) + 偏移量 来生成预测值就行。

		x264中定义了多种方法：
		#define X264_WEIGHTP_NONE            0
		#define X264_WEIGHTP_SIMPLE          1
		#define X264_WEIGHTP_SMART           2
		
		baseline时，b_weighted_pred = b_weighted_bipred = 0，也就是不使用加权预测。

		总结来说，weighted_pred_flag就像是编码器的一个​​智能调光器​​。在遇到场景间亮度剧烈变化时，
		开启它（设为1）可以让编码器自适应地调整参考帧的“亮度”来进行预测，从而高效压缩数据。
		而对于大多数亮度稳定的场景，保持其关闭（设为0）则是一种简单高效的选择。
	*/
    int b_weighted_pred;

	/*
		原理同上，针对B帧。
	*/
    int b_weighted_bipred;

	/*
		对应pps句法中的pic_init_qp_minus26/pic_init_qs_minus26。
		i_pic_init_qp 设置 ​​P 帧​​和​​一般帧​​的亮度分量初始量化参数（QP）的基准值。
		i_pic_init_qs 专门用于设置 ​​SP 帧​​（切换P帧）或​​SI 帧​​（切换I帧）的初始量化参数（QS）。
	*/
    int i_pic_init_qp;
    int i_pic_init_qs;

	/*
		对应pps句法中的chroma_qp_index_offset。
		量化是视频编码中损失信息的关键步骤，QP值决定了量化步长的大小。
		编码器通常会为亮度分量（Y）计算一个QP值（Luma_QP）。chroma_qp_index_offset则用于计算色度分量（Cb和Cr）的QP值：
		Chroma_QP = Luma_QP + chroma_qp_index_offset，
		通过调整 chroma_qp_index_offset，编码器可以独立控制色度分量的压缩强度。
	*/
    int i_chroma_qp_index_offset;

	/*
		对应pps句法中的deblocking_filter_control_present_flag。
		deblocking_filter_control_present_flag是编码器向解码器发出的一个信号，指示是否在 PPS 中提供了​​自定义的去块滤波器参数​​。
		​​设置为 1​​ 提供了​​灵活性​​，允许编码器根据视频内容自适应地调整滤波强度，以期在消除块效应和保留细节之间取得最佳平衡，但会增加少量的码流开销。
		​​设置为 0​​ 则追求​​简洁性​​，使用默认参数，减少了比特开销，但滤波效果是固定不变的。

		b_deblocking_filter_control仅仅影响slice头句法中disable_deblocking_filter_idc/slice_alpha_c0_offset_div2/slice_beta_offset_div2这3个参数。
		slice_alpha_c0_offset_div2/slice_beta_offset_div2是slice头中提供的自定义滤波强度参数。细节在slice头句法中说明。
	*/
    int b_deblocking_filter_control;

	/*
		对应pps句法中的constrained_intra_pred_flag。
		​​1​​: 启用限制。帧内预测​​只能使用​​邻近帧内编码宏块的像素进行预测。
		​​0​​: 禁用限制。帧内预测可以使用任何邻近宏块（包括帧间编码宏块）的像素进行预测
		它的主要作用是​​限制帧内预测的参考像素来源，以确保帧内编码宏块的预测不会使用来自帧间编码宏块的像素，从而增强码流的容错能力​​。

		b_constrained_intra_pred默认是0。
	*/
    int b_constrained_intra_pred;

	/*
		对应pps句法中的redundant_pic_cnt_present_flag。
		目前始终为0。

		redundant_pic_cnt是一个用于标识冗余编码图像的语法元素。它的主要作用是​​提高码流的容错能力​​，
		通过在码流中提供图像的冗余副本，来应对网络传输中可能发生的丢包或错误。
	*/
    int b_redundant_pic_cnt;

	/*
		对应pps句法中的transform_8x8_mode_flag。

		通常h.264的DCT变换都是4x4矩阵，但是也支持8x8矩阵。
		
		b_transform_8x8_mode（通常对应H.264标准中的transform_8x8_mode_flag或x264编码器参数中的b_transform_8x8）
		是一个用于控制是否启用​​8x8变换​​的标志位。它主要影响编码过程中对亮度分量进行变换和预测的块大小。

		当启用时（值为1）​​：编码器在处理宏块时，除了可以使用传统的4x4变换和预测单元外，还可以选择使用8x8的变换块。
		这允许编码器对图像中相对平坦、纹理变化缓慢的区域使用更大的变换块，从而可能用更少的系数表示信号，提高压缩效率。
	*/
    int b_transform_8x8_mode;

} x264_pps_t;

#define x264_cqm_init x264_template(cqm_init)
int  x264_cqm_init( x264_t *h );
#define x264_cqm_delete x264_template(cqm_delete)
void x264_cqm_delete( x264_t *h );
#define x264_cqm_parse_file x264_template(cqm_parse_file)
int  x264_cqm_parse_file( x264_t *h, const char *filename );

#endif
